from django.shortcuts import render

from rest_framework.views import APIView
from django_redis import get_redis_connection
from rest_framework.response import Response
from rest_framework import status
import pickle, base64

from goods.models import SKU
from .serializers import CartSerializer, CartSKUSerializer, CartDeleteSerializer, CartSelectAllSerializer


# Create your views here.
class CartView(APIView):
    """购物车"""

    def perform_authentication(self, request):
        """
        重写此方法让jwt的认证延后,主要是为了让未登录用户也能访问接口
        因为前端在请求时传递的jwt的认证请求头,如果认证不通过就进入不了接口直接响应401 未认证
        """
        pass

    def post(self, request):
        """添加购物车"""
        # 创建序列化器的对象
        serializer = CartSerializer(data=request.data)
        # 校验
        serializer.is_valid(raise_exception=True)
        # 获得数据字典
        validated_data = serializer.validated_data

        # 获得前端传入的数据
        sku_id = validated_data.get('sku_id')
        count = validated_data.get('count')
        selected = validated_data.get('selected')

        try:  # 使用try是因为 要使用request.user就会 去验证, 获取不到, 就会抛出异常, 所以当抛出异常表示未登录用户,直接except往下走
            user = request.user
        except:
            user = None

        # 提前创建响应对象
        response = Response(serializer.data, status=status.HTTP_201_CREATED)

        if user and user.is_authenticated:  # 本网站的登录用户
            """登录情况操作redis购物车数据库"""

            # 创建redis连接对象
            redis_conn = get_redis_connection('cart')
            # Redis管道法  漂亮
            pl = redis_conn.pipeline()

            # sku_id  count 存储到hash字典
            # 先把当前用户的购物车数据获取出来
            # 判断当前要添加的sku_id 在hash字典中是否已存在,如果不存,直接添加
            # 如果sku_id在hash字典中已存在,要把原sku_id的count取出累加上本次count
            """
                         获取所有哈希中的小字典({"sku_id": count})
            redis_dict = redis_conn.hgetall('cart_%s' % user.id)
            # 遍历哈希字典遍历小字典  得到red_sku_id键
            for sku_id_bytes in redis_dict:
                if sku_id == int(sku_id_bytes):
                    origin_count = redis_dict[red_sku_id]
                    count = count + int(origin_count)


            redis_conn.hmset('cart_%s' % user.id, sku_id, count)
            """
            # TODO: red_sku_id 是啥??
            # hincrby(key取hash字典的key, sku_id, count)  自动解决增量问题
            pl.hincrby('cart_%s' % user.id, sku_id, count)

            # sku_id的选中状态存储到set集合中
            if selected:  # 只有是勾选的商品才添加到set中
                pl.sadd('selected_%s' % user.id, sku_id)  # TODO:sku_id是整型??

            # 执行管道
            pl.execute()

            # 响应
            # return Response(serializer.data, status=status.HTTP_201_CREATED)



        else:

            """
            {
                sku_id16: {
                    'count': 1,
                    'selected': True
                },
                sku_id15: {
                    'count': 1,
                    'selected': True
                }
            }
            """
            """未登录操作cookie购物车数据"""
            # 获取当前cookie中已有的购物车数据
            cart_str = request.COOKIES.get('cart')

            if cart_str:  # 如果当前cookie中已经有数据,然后再进行转换

                """
                   json字符串 --> (encode)bytes类型 --> (base64.b64decode)bytes类型的ASCII  --> (pickle.loads)字典
                """

                # 把cookie的字符串转换成bytes类型的字符串  encode()
                cart_str_bytes = cart_str.encode()
                # 把bytes类型的字符串转换成bytes类型的ASCII  base64.b64decode()
                cart_ascii_bytes = base64.b64decode(cart_str_bytes)
                # 把bytes类型的ASCII转换成字典  pickle.loads()
                cart_dict = pickle.loads(cart_ascii_bytes)
            else:  # 如果之前cookie中没有购物车数据,准备一个空字典
                cart_dict = {}

            # 如果添加的商品是已存在的,做增量计算
            for cookie_sku_id in cart_dict:  # 遍历字典,获取所有的键
                # 非空判断
                if sku_id == cookie_sku_id:
                    # 存在进行增值操作
                    origin_count = cart_dict[cookie_sku_id]['count']
                    count += origin_count  # count = count + origin_count

            # 包装cookie购物车数据
            cart_dict[sku_id] = {
                'count': count,
                'selected': selected
            }

            # 把字典转字符串(反向操作)
            """
                字典 --> (pickle.dumps)bytes类型的ASCII --> (base64.b64encode)bytes类型的字符串 --> (decode)json字符串
            """
            # 把字典转换成bytes类型的ASCII
            cart_ascii_bytes = pickle.dumps(cart_dict)
            # 把bytes类型的ASCII转换成bytes类型的字符串
            cart_str_bytes = base64.b64encode(cart_ascii_bytes)
            # 把换成bytes类型的字符串转换成字符串
            cart_str = cart_str_bytes.decode()

            # 创建response对象, 响应cookie
            # response = Response(serializer.data, status=status.HTTP_201_CREATED)
            response.set_cookie('cart', cart_str)

        # 响应
        return response  # ==>  Response(serializer.data)

    def get(self, request):
        """查询购物车(查)"""
        # Redis和cookies响应格式一样, 方便前端
        # 获取用户对象
        try:  # 使用try是因为 要使用request.user就会 去验证, 获取不到, 就会抛出异常, 所以当抛出异常表示未登录用户,直接except往下走
            user = request.user
        except:
            user = None

        if user and user.is_authenticated:  # 本网站的登录用户
            """登录用户操作redis购物车数据"""
            # 创建redis连接对象
            redis_conn = get_redis_connection('cart')
            # 获取hash大字典{sku_id1: count, sku_id2: count}
            redis_cart = redis_conn.hgetall('cart_%s' % user.id)
            # 获取set集合数据 {sku_id1}  SMEMBERS
            redis_selected = redis_conn.smembers('selected_%s' % user.id)
            """  cookie购物车类型
            {
                sku_id16: {
                    'count': 1,
                    'selected': True
                },
                sku_id15: {
                    'count': 1,
                    'selected': True
                }
            }
            """
            # 下面是要包成cookie购物车类型
            cart_dict = {}  # 定义一个空字典来包
            # 遍历     items具有获得所有键值对并解包
            for sku_id, count in redis_cart.items():
                #  字典的键可以为整型,字符串和元组
                cart_dict[int(sku_id)] = {
                    'count': int(count), # 强制转换
                    'selected': sku_id in redis_selected  # 如果sku_id在set集合数据中,返回True,还有两者都是bytes类型
                }


        else:
            """未登录操作cookie购物车"""
            # # 获取当前cookie中已有的购物车数据
            cart_str = request.COOKIES.get('cart')
            # 非空判断
            if cart_str:
                # 简写把字符串转化为 字典
                """
                   json字符串 --> (encode)bytes类型 --> (base64.b64decode)bytes类型的ASCII  --> (pickle.loads)字典
                """
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                # 如果之前cookie中没有购物车数据,准备一个空字典
                cart_dict = {}


        # 获取出字典中的所有sku_id
        sku_ids = cart_dict.keys()  # keys取出字典所有的键
        # 通过 过滤查询  SKU模型中的所有id是否在sku_ids中 来判断是否勾选
        skus = SKU.objects.filter(id__in=sku_ids)  # 注意点: filter返回的查询集可以为0个或者多个
        for sku in skus:  # 循环遍历出一个个 sku_id
        # 给sku模型添加属性  取出cart_dict字典中的 count
            sku.count = cart_dict[sku.id]['count']
        # 给sku模型添加属性  取出cart_dict字典中的 selected
            sku.selected = cart_dict[sku.id]['selected']

        # 创建序列化器进行序列化  同一序列化  skus是查询集 (复数) --> 赋值给 instance, 因为要进行序列化
        serializer = CartSKUSerializer(skus, many=True)
        # 响应
        return Response(serializer.data)


    def put(self, request):
        """修改购物车(改)"""
        # 创建序列化器的对象
        serializer = CartSerializer(data=request.data)
        # 校验
        serializer.is_valid(raise_exception=True)
        # 获得数据字典
        validated_data = serializer.validated_data

        # 获得前端传入的数据
        sku_id = validated_data.get('sku_id')
        count = validated_data.get('count')
        selected = validated_data.get('selected')

        # 获取用户对象
        try:  # 使用try是因为 要使用request.user就会 去验证, 获取不到, 就会抛出异常, 所以当抛出异常表示未登录用户,直接except往下走
            user = request.user
        except:
            user = None

        # 提前创建响应对象
        response = Response(serializer.data)

        if user and user.is_authenticated:  # 本网站的登录用户
            """登录用户操作redis购物车数据"""
            # 创建 Redis连接对象
            redis_conn = get_redis_connection('cart')
            #  # Redis管道法  漂亮
            pl = redis_conn.pipeline()

            # 直接覆盖商品的购买数量, 不需要增量
            pl.hset('cart_%s' % user.id, sku_id, count)

            # 修改它的勾选状态,如果勾选就添加到set集合中,否则从set集合中移除
            if selected:
                # 勾选   添加到set集合中
                pl.sadd('selected_%s' % user.id, sku_id)
            else:
                # 不勾选   从set集合中删除
                pl.srem('selected_%s' % user.id, sku_id)

            pl.execute()  # 执行管道

        else:
            """未登录用户操作cookie购物车"""
            # 获取当前cookie中已有的购物车数据
            cart_str = request.COOKIES.get('cart')
            if cart_str:  # 如果当前cookie中已经有数据,然后再进行转换
                """
                    json --> (encode)bytes类型字符串 --> (base64.b64decode)bytes ASCII类型 --> (pickle.loads) 字典
                """
                # 把cookie的字符串转换成bytes类型的字符串  encode()
                cart_str_bytes = cart_str.encode()
                # 把bytes类型的字符串转换成bytes类型的ASCII  base64.b64decode()
                cart_ascii_bytes = base64.b64decode(cart_str_bytes)
                # 把bytes类型的ASCII转换成字典  pickle.loads()
                cart_dict = pickle.loads(cart_ascii_bytes)
            else:  # 如果之前cookie中没有购物车数据,准备一个空字典
                cart_dict = {}

            # 包装cookie购物车数据
            cart_dict[sku_id] = {
                'count': count,
                'selected': selected
            }

            # 把字典转字符串

            """
                字典 --> (pickle.dumps)bytes类型的ASCII --> (base64.b64encode)bytes类型的字符串 --> (decode)json字符串
            """
            # 把字典转换成bytes类型的ASCII
            cart_ascii_bytes = pickle.dumps(cart_dict)
            # 把bytes类型的ASCII转换成bytes类型的字符串
            cart_str_bytes = base64.b64encode(cart_ascii_bytes)
            # 把换成bytes类型的字符串转换成字符串
            cart_str = cart_str_bytes.decode()

            # 创建response对象
            # response = Response(serializer.data, status=status.HTTP_201_CREATED)
            # 设置cookies
            response.set_cookie('cart', cart_str)
        # 响应
        return response  #  ==>  Response(serializer.data)


    def delete(self, request):
        """删除购物车(删)"""
        # 创建序列化器进行反序列化校验
        #                    序列化器      反序列化 = 前端请求json数据
        serializer = CartDeleteSerializer(data=request.data)
        # 校验                 自动抛出异常
        serializer.is_valid(raise_exception=True)  # 校验序列化器中的字段的属性 (先执行)
        # 获取 反序列化中的 sku_id
        sku_id = serializer.validated_data.get('sku_id')

        # 获取用户对象
        try:  # 使用try是因为 要使用request.user就会 去验证, 获取不到, 就会抛出异常, 所以当抛出异常表示未登录用户,直接except往下走
            user = request.user
        except:
            user = None

        if user and user.is_authenticated:
            """登录用户操作redis购物车数据"""
            # 创建redis连接对象
            redis_conn = get_redis_connection('cart')
            pl = redis_conn.pipeline()
            # 把sku_id对应的商品从hash字典中移除
            pl.hdel('cart_%s' % user.id, sku_id)
            # 把set集合中sku_id移除(移除它的勾选状态)
            pl.srem('selected_%s' % user.id, sku_id)
            # 执行管道
            pl.execute()
            # 响应
            return Response(status=status.HTTP_204_NO_CONTENT)

        else:
            """未登录操作cookie购物车数据"""
            # 获取cookie中的购物车数据
            cart_str = request.COOKIES.get('cart')
            # 非空判断
            if cart_str:
                # cookie中的购物车有数据, 则进行 字典化转换
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                # cookie中的购物车没有数据, 直接响应
                # cart_dict = {}
                # 如果cookies为空直接响应
                return Response({'message': 'cookie为空'}, status=status.HTTP_400_BAD_REQUEST)

            """  cookie购物车类型
            {
                sku_id16: {
                    'count': 1,
                    'selected': True
                },
                sku_id15: {
                    'count': 1,
                    'selected': True
                }
            }
            """
            # 判断要删除的sku_id在字典中是否存在
            if sku_id in cart_dict:
                # 字典删除操作
                del cart_dict[sku_id]

            # 把字典转换成字符串
            cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()
            # 创建响应对象
            response = Response(status=status.HTTP_204_NO_CONTENT)
            # 设置cookie --> 键值对
            response.set_cookie('cart', cart_str)

            # 响应, 并把cookie一并携带
            return response

class CartSelectAllView(APIView):
    """购物车全选"""

    def perform_authentication(self, request):
        """重写此方法里面什么也不做,目的,延后做认证,为了未登录用户也能进入到相应的请求方法"""
        pass

    def put(self, request):
        """购物车全选修改操作"""

        # 创建序列化器进行反序列化校验
        #                    序列化器      反序列化 = 前端请求json数据
        serializer = CartSelectAllSerializer(data=request.data)
        # 校验                 自动抛出异常
        serializer.is_valid(raise_exception=True)  # 校验序列化器中的字段的属性 (先执行)
        # 获取 反序列化中的 selected
        selected = serializer.validated_data.get('selected')

        # 获取用户对象
        try:  # 使用try是因为 要使用request.user就会 去验证, 获取不到, 就会抛出异常, 所以当抛出异常表示未登录用户,直接except往下走
            user = request.user
        except:
            user = None


        # 创建响应对象
        response = Response({'message': 'ok'})

        if user and user.is_authenticated:  # 本网站的登录用户
            """登录用户操作redis购物车"""
            # 创建redis连接对象
            redis_conn = get_redis_connection('cart')

            # 把hash字典中的所有购物车商品数据取出
            redis_cart_dict = redis_conn.hgetall('cart_%s' % user.id)
            # 获取出字典中的所有key
            sku_ids = redis_cart_dict.keys()

            # 如果是全选就把哈希字典里面所有的 sku_id 添加到 存放selected的 set集合中
            if selected:  # 全选 selected为True
                redis_conn.sadd('selected_%s' % user.id, *sku_ids)   # *xxx  解包
            else:
                # 如果是取消全选,就把所有的sku_id从set集合中移除
                redis_conn.srem('selected_%s' % user.id, *sku_ids)


        else:
            """未登录操作cookie购物车"""
            # 获取cookie
            cart_str = request.COOKIES.get('cart')
            # 只要是前端传来的 要非空判断
            if cart_str:
                # 把cart_str转换成cart_dict (json数据字典化)
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                # cart_dict = {}
                # 前端传来的cookie为空, 响应400状态码
                return Response({'message': 'cookie为空'}, status=status.HTTP_400_BAD_REQUEST)
            """  cookie购物车类型
            {
                sku_id16: {
                    'count': 1,
                    'selected': True
                },
                sku_id15: {
                    'count': 1,
                    'selected': True
                }
            }
            """

            # 遍历字典修改购物车中所有商品的勾选状态
            for sku_id in cart_dict:  # 遍历cookie, 得到key==>sku_id
                cart_dict[sku_id]['selected'] = selected  # 取出小字典中的selected, 把前端传过来的状态赋值给selected
            # 把cart_dict 转换成cart_str
            cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()

            # 把cookie响应回去
            response.set_cookie('cart', cart_str)
         # 响应
        return response


